﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml.Linq;
using Microsoft.SharePoint.Utilities;
using Sapphire.Linq.FieldMaps;

namespace Sapphire.Linq.Framework
{
    internal class CAMLBuilder
    {
        private readonly IPropertyMapsContext _mapsContext;

        private CAMLBuilder()
        {
        }

        private CAMLBuilder(IPropertyMapsContext mapsContext)
        {
            _mapsContext = mapsContext;
        }

        private XElement GetFieldRef(Type type, string propertyName)
        {
            return _mapsContext.GetFieldRef(type, propertyName);
        }

        public static CAMLBuilder Create(IPropertyMapsContext mapsContext)
        {
            return new CAMLBuilder(mapsContext);
        }

        public XElement DynamicUnary(string nodeValue,
            MemberExpression left, Expression right)
        {
            return XElement.Parse(
              string.Format(
                  "<{0}>{1}<Value Type=\"{2}\">{3}</Value></{0}>",
                      nodeValue,
                      GetFieldRef(left.Expression.Type, left.Member.Name),
                      GetType(GetValue(right).GetType()),
                      GetValue(right)));
        }

        public XElement DynamicUnary(ExpressionType nodeType,
            MemberExpression left, Expression right)
        {
            if (nodeType == ExpressionType.NotEqual && GetValue(right) == null)
                return XElement.Parse(
                    string.Format(
                    "<IsNotNull>{0}</IsNotNull>", GetFieldRef(right.Type, left.Member.Name)));

            if (nodeType == ExpressionType.Equal && GetValue(right) == null)
                return XElement.Parse(
                    string.Format(
                    "<IsNull>{0}</IsNull>", GetFieldRef(right.Type, left.Member.Name)));


            return DynamicUnary(GetOperand(nodeType), left, right);
        }

        private object GetValue(Expression right)
        {
            if (right.Type == typeof(DateTime) || right.Type == typeof(DateTime?))
                return SPUtility.CreateISO8601DateTimeFromSystemDateTime(((DateTime)Expression.Lambda(right).Compile().DynamicInvoke()));

            return Expression.Lambda(right).Compile().DynamicInvoke();
        }

        private string GetOperand(ExpressionType nodeType)
        {
            switch (nodeType)
            {
                case ExpressionType.Equal:
                    return "Eq";
                case ExpressionType.NotEqual:
                    return "Neq";
                case ExpressionType.GreaterThan:
                    return "Gt";
                case ExpressionType.GreaterThanOrEqual:
                    return "Geq";
                case ExpressionType.LessThan:
                    return "Lt";
                case ExpressionType.LessThanOrEqual:
                    return "Leq";
                default:
                    throw new ArgumentOutOfRangeException("nodeType");
            }
        }

        private string GetType(Type getType)
        {
            if (getType == typeof(string))
                return "Text";

            if (getType == typeof(int))
                return "Number";

            if (getType == typeof(DateTime))
                return "DateTime";

            throw new ArgumentOutOfRangeException("nodeType");
        }

        public XElement DynamicUnary(string nodeValue, MemberInfo left)
        {
            return XElement.Parse(
                string.Format("<{0}>{1}</{0}>", nodeValue, GetFieldRef(null, left.Name)));
        }

        public XElement OrderBy(MethodCallExpression methodCallExpression, LambdaExpression lambdaExpression)
        {
            var fieldRef = GetFieldRef(
                lambdaExpression.Parameters[0].Type,
                ((MemberExpression)(lambdaExpression.Body)).Member.Name);

            if (methodCallExpression.Method.Name != "OrderBy")
                fieldRef.SetAttributeValue("Ascending", "DESC");

            return fieldRef;
        }

        public IEnumerable<XElement> ViewFields(Type getEntityType)
        {
            return TypeDescriptor.GetProperties(getEntityType)
                .Cast<PropertyDescriptor>()
                .Select(p => GetFieldRef(getEntityType, p.Name));
        }
    }
}